package com.algo.FJSP.GaForFJSP;

import com.algo.FJSP.common.*;

import java.util.*;
import java.util.stream.IntStream;
/**
 * @Author: guoxing
 * @Email: gx_study2022@163.com
 * @Description:
 * @Date: 2024/7/4 22:47
 * @ClassName: GAUtils
 **/
public class GAUtils {
    private Random random=new Random();

    /**
     * @Description 随机生成一个初始的调度计划
     * @author guoxing
     * @date 2024/7/5 9:07
     * @param dataManage 所有数据
     * @return ScheduleResult 调度计划
     */
    public ScheduleResult getRandomSchedule(DataManage dataManage){
        ArrayList<String> operations=new ArrayList<String>(dataManage.getOperations());
        Collections.shuffle(operations);
        //先生成工序编码，在生成设备选择编码
        int[] osCode=new int[operations.size()];
        for (int i=0;i<operations.size();i++){
            String[] split=operations.get(i).split("-");
            osCode[i]=Integer.parseInt(split[0]);
        }
        int[] csCode=new int[dataManage.getOperationNum()];
        for (int i=0;i<osCode.length;i++){
            List<Integer> machines=dataManage.getOperationCodeAndMachineListMap().get(dataManage.getOperations().get(i));
            csCode[i]=machines.get(new  Random().nextInt(machines.size()));
        }
        //生成一个可行的调度计划
        return getScheduleResult(csCode,osCode,dataManage);
    }

    /** 
     * @Description 将一个个体的编码变成一个调度计划--解码
     * @author guoxing
     * @date 2024/7/5 21:30 
     * @param csCode 机器选择序列:[1,4,5,8,5,4,3,2]
     * @param osCode 工序排序序列:[1,4,5,8,5,4,3,2]
     * @param dataManage 总数据
     * @return ScheduleResult 形成的调度结果
     */
    public ScheduleResult getScheduleResult(int[] csCode,int[] osCode,DataManage dataManage){
        //对每个工序出现的次数进行统计
        int[] operationOrderCount=new int[dataManage.getJobNum()];
        //机器
        ArrayList<Machine>machines=new ArrayList<Machine>(dataManage.getMachineNum());
        for(int i=0;i<dataManage.getMachineNum();i++){
            Machine m=new Machine();
            m.setNumber(i);
            m.setOperations(new ArrayList<>());
            machines.add(m);
        }
        //作业
        ArrayList<Job>jobs=new ArrayList<Job>(dataManage.getJobNum());
        for(int i=0;i<dataManage.getJobNum();i++){
            Job j=new Job();
            j.setNumber(i);
            j.setOperations(new ArrayList<>());
            jobs.add(j);
        }

        for (int jobNumber:osCode){
            //获取工序编号
            String operationCode=jobNumber+"-"+operationOrderCount[jobNumber];
            //该工序选择的机器编号
            int csCodeIndex=dataManage.getOperationCodeAndIndexMap().get(operationCode);
            int machineNumber=csCode[csCodeIndex];

            //该工序在机器上的处理时间
            int processTime=dataManage.getOperationProcessTimeMap().get(operationCode+"-"+machineNumber);
            Machine machine=machines.get(machineNumber);
            Job job=jobs.get(jobNumber);
            //判断作业紧前工序和机器紧前工序的完工时间那个更晚
            int latestCompleteTime=Math.max(machine.getCompleteTime(),job.getCompleteTime());
            //封装一个工序
            Operation operation=new Operation();
            operation.setJobNumber(jobNumber);
            operation.setOrder(operationOrderCount[jobNumber]);
            operation.setCode(operationCode);
            operation.setReleaseTime(latestCompleteTime);
            operation.setCompleteTime(latestCompleteTime + processTime);
            operation.setMachineNum(machineNumber);
            operation.setProcessTime(processTime);

            //更新机器序列和机器的最大完工时间
            machine.getOperations().add(operation);
            machine.setCompleteTime(operation.getCompleteTime());

            //更新作业序列和最大完工时间
            job.getOperations().add(operation);
            job.setCompleteTime(operation.getCompleteTime());
            //作业工序次数计算
            operationOrderCount[jobNumber]++;
        }

        //获得改调度计划的最大完工时间
        int makespan=machines.stream().max(Comparator.comparing(Machine::getCompleteTime)).orElseThrow().getCompleteTime();
        ScheduleResult scheduleResult=new ScheduleResult();
        scheduleResult.setMachines(machines);
        scheduleResult.setJobs(jobs);
        scheduleResult.setMakespan(makespan);
        return scheduleResult;
    }

    /***
     * @Description 初始化种群
     * @author guoxing
     * @date 2024/7/7 13:42
     * @param dataManage 所有数据
     * @param initialPopSize 种群大小
     * @return ArrayList<Individual> 生成的初始种群
     */
    public ArrayList<Individual> generateInitialPopulation(DataManage dataManage, int initialPopSize){
        ArrayList<Individual> individuals=new ArrayList<Individual>(initialPopSize);
        ArrayList<String> operations=new ArrayList<String>(dataManage.getOperations());
        for (int k=0;k<initialPopSize;k++){
            Collections.shuffle(operations);
            //先生成osCode，再根据osCode生成对应的csCode
            int[] osCode=new int[operations.size()];
            for (int i=0;i< operations.size();i++){
                String[] split=operations.get(i).split("-");
                osCode[i]=Integer.parseInt(split[0]);
            }
            int[] csCode=new int[dataManage.getOperationNum()];
            for (int i=0;i<osCode.length;i++){
                List<Integer> machines=dataManage.getOperationCodeAndMachineListMap().get(dataManage.getOperations().get(i));
                //随机选择可用机器
                csCode[i]=machines.get(new Random().nextInt(machines.size()));
            }
            Individual individual=new Individual();
            individual.setCsCode(csCode);
            individual.setOsCode(osCode);
            individuals.add(individual);
        }

        return individuals;
    }

    /**
     * @Description 计算适应度值-解码
     * @author guoxing
     * @date 2024/7/7 13:42
     * @param individual 个体
     * @param dataManage 所有数据
     * @return double 或者返回值可以是int
     */
    public double decode(Individual individual,DataManage dataManage){
        ScheduleResult scheduleResult=getScheduleResult(individual.getCsCode(),individual.getOsCode(),dataManage);
        return scheduleResult.getMakespan();
    }

    /**
     * @Description 选择操作
     * @author guoxing
     * @date 2024/7/7 15:14
     * @param individuals 被选择的种群
     * @param eSize 选择过程中，精英选择的余量，余下的采用竞标赛选择的方式进行选择
     * @param tNum 要进行锦标赛的个体数
     * @return ArrayList<Individual>
     */
    public ArrayList<Individual> selection(ArrayList<Individual>individuals,int eSize,int tNum) throws Exception {
        int selectNum=individuals.size()/2;
        if (eSize>=selectNum){
            System.out.println("eSize: " + eSize);
            System.out.println("selectNum/2: " + selectNum/2);
            throw new Exception("eSize不能比selectNum/2大");
        }
        HashSet<Individual> individualHashSet=new HashSet<Individual>(individuals);
        ArrayList<Individual>selectedIndividuals=new ArrayList<Individual>();
        //先进行精英选择，选择eSize个个体
        for (int i=0;i<eSize;i++){
            //取适应度值的倒数则用这个
//            Individual individual=individuals.stream().max(Comparator.comparing(Individual::getFitness)).orElseThrow();
            Individual individual=individuals.stream().min(Comparator.comparing(Individual::getFitness)).orElseThrow();
            selectedIndividuals.add(individual);
            individualHashSet.remove(individual);
            individuals=new ArrayList<>(individualHashSet);
        }
        if (tNum>individuals.size() && tNum<2){
            //参与锦标赛的个体数量不能超过此时还剩下的个体数量，此外，参与锦标赛的个体数量不能小于2个
            throw new Exception("error");
        }
        //在进行锦标赛选择选出tSize个个体
        for (int i = 0;i <selectNum-eSize;i++) {
            Collections.shuffle(individuals);
            Individual bestIndividual=individuals.get(0);
            for (int j=1;j<tNum;j++){
                if (bestIndividual.getFitness()>individuals.get(j).getFitness()){
                    bestIndividual=individuals.get(j);
                }
            }
            //如果一个个体的适应度很高，可能会被重复选中
            selectedIndividuals.add(bestIndividual.clone());
        }
        return selectedIndividuals;
    }



    /** 
     * @Description 交叉操作，该操作需要两个父代个体
     * @author guoxing
     * @date 2024/7/10 22:23 
     * @param parent1 父代第一个个体
     * @param parent2 父代第二个个体
     * @param jobNum 作业数量-工件个数
     * @param operationNum 工序的总数量
     * @return List<Individual> 交叉过后的子代
     */
    public List<Individual> crossover(Individual parent1,Individual parent2,int jobNum,int operationNum) {
        //两个父代的编码
        int[] p1csCode=parent1.getCsCode(),p2csCode=parent2.getCsCode();
        int[] p1osCode=parent1.getOsCode(),p2osCode=parent2.getOsCode();
        //两个子代的编码
        int[] o1csCode=new int[operationNum],o2csCode=new int[operationNum];
        int[] o1osCode=new int[operationNum],o2osCode=new int[operationNum];

        //产生cs编码的交叉点
        int random1=random.nextInt(operationNum-1);
        int random2=random.nextInt(operationNum-random1)+random1;
        //用于完成os编码的准备数据
        for(int i=0;i<operationNum;i++){
            if(random1<=i&&i<random2){
                //第二段，p1给o2，p2给o1
                o1csCode[i]=p2csCode[i];
                o2csCode[i]=p1csCode[i];
            }else {
                //第一段或者第三段，P1给o1，p2给o2
                o1csCode[i]=p1csCode[i];
                o2csCode[i]=p2csCode[i];
            }
        }
        //用于完成OS编码的准备数据
        //随机将作业划分为两部分
        List<Integer>jobList=new ArrayList<>(IntStream.range(0,jobNum).boxed().toList());
        Collections.shuffle(jobList);
        int randomIndex=random.nextInt(jobList.size()-1)+1;
        Set<Integer> jobPartOne=new HashSet<>(jobList.subList(0,randomIndex)),jobPartTwo=new HashSet<>(jobList.subList(randomIndex,jobList.size()));
        if (random.nextDouble()>0.5){
            pox(operationNum,p1osCode,p2osCode,o1osCode,o2osCode,jobPartOne,jobPartTwo);
        }else {
            jbx(operationNum,p1osCode,p2osCode,o1osCode,o2osCode,jobPartOne,jobPartTwo);
        }
        List<Individual> offsprings=new ArrayList<>(2);
        offsprings.add(new Individual(o1csCode,o1osCode));
        offsprings.add(new Individual(o2csCode,o2osCode));
        return offsprings;
    }


    /** 
     * @Description POX交叉方式
     * 1）随机划分工件集为两个非空子集J1 和J2；
     * 2）复制p1中属于工件集J1 中工件的工序到c1，复制p2中属于工件集J1 中工件的工序到c2，保留它们的位置；
     * 3）复制p1中属于工件集J2 中工件的工序到c2，复制p2中属于工件集J2 中工件的工序到c1，保留它们的顺序。
     * @author guoxing
     * @date 2024/7/10 22:24 
     * @param operationNum 工序的总数量
     * @param p1osCode 父代的第一个工序编码
     * @param p2osCode 父代的第二个工序编码
     * @param o1osCode 
     * @param o2osCode 
     * @param jobPartOne 
     * @param jobPartTwo 
     
     */
    public void pox(int operationNum,int[] p1osCode,int[] p2osCode,int[]o1osCode,int[]o2osCode,Set<Integer> jobPartOne,Set<Integer> jobPartTwo){
        //第一次遍历完成之后，o1\o2未被使用的位置
        ArrayList<Integer> o1osIndexNotUsed=new ArrayList<>(operationNum),o2osIndexNotUsed=new ArrayList<>(operationNum);
        for(int i=0;i<operationNum;i++){
            //os部分交叉
            if(jobPartOne.contains(p1osCode[i])){
                o1osCode[i]=p1osCode[i];
            }else {
                o1osIndexNotUsed.add(i);
            }
            if (jobPartTwo.contains(p2osCode[i])){
                o2osCode[i]=p2osCode[i];
            }else {
                o2osIndexNotUsed.add(i);
            }
        }
        //完成os的交叉
        int index2=0,index1=0;
        for (int i=0;i<operationNum;i++){
            if(jobPartOne.contains(p1osCode[i])){
                o2osCode[o2osIndexNotUsed.get(index2)]=p1osCode[i];
                index2++;
            }
            if (jobPartTwo.contains(p2osCode[i])){
                o1osCode[o1osIndexNotUsed.get(index1)]=p2osCode[i];
                index1++;
            }
        }
    }


    /** 
     * @Description JBX交叉
     * @author guoxing
     * @date 2024/7/10 22:24 
     * @param operationNum 
     * @param p1osCode 
     * @param p2osCode 
     * @param o1osCode 
     * @param o2osCode 
     * @param jobPartOne 
     * @param jobPartTwo 
     
     */
    public void jbx(int operationNum,int[] p1osCode,int[] p2osCode,int[]o1osCode,int[]o2osCode,Set<Integer> jobPartOne,Set<Integer> jobPartTwo){
        //第一次遍历完成之后，o1、o2未被使用的位置
        ArrayList<Integer> o1osIndexNotUse = new ArrayList<>(), o2osIndexNotUse = new ArrayList<>();
        for (int i = 0; i < operationNum; i++) {
            //os部分交叉
            if (jobPartOne.contains(p1osCode[i])) {
                o1osCode[i] = p1osCode[i];
            } else {
                o1osIndexNotUse.add(i);
            }
            if (jobPartTwo.contains(p2osCode[i])) {
                o2osCode[i] = p2osCode[i];
            } else {
                o2osIndexNotUse.add(i);
            }
        }
        //完成os的交叉
        int index2 = 0, index1 = 0;
        for (int i = 0; i < operationNum; i++) {
            if (jobPartOne.contains(p1osCode[i])) {
                o2osCode[o2osIndexNotUse.get(index2)] = p1osCode[i];
                index2++;
            }
            if (jobPartTwo.contains(p2osCode[i])) {
                o1osCode[o1osIndexNotUse.get(index1)] = p2osCode[i];
                index1++;
            }
        }
    }


    /** 
     * @Description 变异操作
     * @author guoxing
     * @date 2024/7/10 22:24 
     * @param individual 需要被变异的个体
     * @param dataManage 整体数据 辅助变异正常进行
     
     */
    public void mutation(Individual individual,DataManage dataManage)throws Exception{
        int[] csCode=individual.getCsCode(),osCode=individual.getOsCode();
        //CS编码的变异，依次访问每个元素，以50%的概率改变工序的机器选择
        for(int i=0;i<dataManage.getOperationNum();i++){
            if (random.nextDouble()<0.5){
                int currentMachine=csCode[i];
                String code=dataManage.getOperations().get(i);
                List<Integer>machines=new ArrayList<Integer>(dataManage.getOperationCodeAndMachineListMap().get(code));
                if (machines.size()==1){
                    continue;
                }
                if (machines.removeIf(machineNumber->machineNumber==currentMachine)){
                    //随机在去除了当前机器的机器列表中选择一个机器
                    csCode[i]=machines.get(random.nextInt(machines.size()));
                }else {
                    throw new Exception("变异部分出错了");
                }
            }
        }
        int random1=random.nextInt(osCode.length),random2=random.nextInt(osCode.length);
        int temp=osCode[random1];
        osCode[random1]=osCode[random2];
        osCode[random2]=temp;
    }


    /** 
     * @Description 所有的交叉和变异操作
     * @author guoxing
     * @date 2024/7/10 22:24
     * @param selectIndividuals 选择后的种群
     * @param crossoverProbability 交叉概率
     * @param mutationProbability 变异概率
     * @param dataManage 整体数据辅助进行
     * @return ArrayList<Individual> 
     */
    public ArrayList<Individual>popCrossoverAndMutation(ArrayList<Individual>selectIndividuals,double crossoverProbability,double mutationProbability,DataManage dataManage)throws Exception{
        ArrayList<Individual> offSprings=new ArrayList<Individual>(selectIndividuals.size()*2);
        for (int j=0;j<selectIndividuals.size();j++){
            //这里是按顺序进行交叉也可以采用随机选取两个个体的方式进行交叉
            Individual parent1=selectIndividuals.get(j);
            Individual parent2=j==selectIndividuals.size()-1?selectIndividuals.get(0):selectIndividuals.get(j+1);
            Individual offSpring1;
            Individual offSpring2;
            if (random.nextDouble()<crossoverProbability){
                List<Individual> crossover=crossover(parent1,parent2,dataManage.getJobNum(),dataManage.getOperationNum());
                offSpring1=crossover.get(0);
                offSpring2=crossover.get(1);
            }else {
                offSpring1=parent1.clone();
                offSpring2=parent2.clone();
            }
            if (random.nextDouble()<mutationProbability){
                mutation(offSpring1,dataManage);
            }
            if (random.nextDouble()<mutationProbability){
                mutation(offSpring2,dataManage);
            }
            offSprings.add(offSpring1);
            offSprings.add(offSpring2);
        }
        return offSprings;
    }
}
